【AWS CDK】API Gateway で S3 をプロキシしてオブジェクトをアップロードしてみた
はじめに
テントの中から失礼します、CX事業本部のてんとタカハシです!
API Gateway では、S3 をプロキシして、オブジェクトのダウンロードやアップロード、削除など様々なことができます。単純な CRUD 操作であれば、Lambda を実装することなく実現可能なので結構便利です。
本記事では、API Gateway で S3 をプロキシして、Bucket に格納されているオブジェクトをアップロードする REST API を CDK で作成してみます。
下記のチュートリアルをガッツリ参考していますので、細かい説明についてはこちらをご参照ください。
他の記事で、オブジェクトのダウンロード、削除、一覧取得にもチャレンジしています。
本記事及び、上記の記事にて試した機能を全て搭載した REST API のソースコードを下記リポジトリに置いています。
GitHub - iam326/api-gateway-proxy-to-s3-by-cdk
環境
環境は下記の通りです。
$ cdk --version 1.102.0 (build a75d52f) $ yarn --version 1.22.10 $ node --version v14.7.0
実装
- オブジェクトを格納するための S3 Bucket を作成する
- API Gateway で REST API を作成する
- 上記 API にリソース
/users/{userId}/files/{fileName}
を作成する - 上記リソースに PUT メソッドを作成して、S3 をプロキシする
import * as cdk from '@aws-cdk/core'; import * as apigateway from '@aws-cdk/aws-apigateway'; import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; export class ApiGatewayProxyToS3ByCdkStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const projectName: string = this.node.tryGetContext('projectName'); // ★ S3 const bucket = new s3.Bucket(this, 'Bucket', { bucketName: `${projectName}-bucket`, }); // ★ API Gateway const restApiRole = new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), path: '/', }); bucket.grantReadWrite(restApiRole); const restApi = new apigateway.RestApi(this, 'RestApi', { restApiName: `${projectName}-api`, deployOptions: { stageName: 'v1', loggingLevel: apigateway.MethodLoggingLevel.INFO, dataTraceEnabled: true, }, defaultCorsPreflightOptions: { allowOrigins: apigateway.Cors.ALL_ORIGINS, allowMethods: ['POST', 'OPTIONS', 'PUT', 'DELETE'], statusCode: 200, }, }); // リソースを作成する `/users/{userId}/files/{fileName}` const users = restApi.root.addResource('users'); const userId = users.addResource('{userId}'); const files = userId.addResource('files'); const fileName = files.addResource('{fileName}'); // オブジェクトをアップロードするための PUT メソッドを作成する fileName.addMethod( 'PUT', new apigateway.AwsIntegration({ service: 's3', integrationHttpMethod: 'PUT', // アップロード先を指定する path: `${bucket.bucketName}/{folder}/{object}`, options: { credentialsRole: restApiRole, passthroughBehavior: apigateway.PassthroughBehavior.WHEN_NO_MATCH, requestParameters: { // メソッドリクエストのパスパラメータ userId を 統合リクエストのパスパラメータ folder にマッピングする 'integration.request.path.folder': 'method.request.path.userId', // メソッドリクエストのパスパラメータ fileName を 統合リクエストの object にマッピングする 'integration.request.path.object': 'method.request.path.fileName', }, integrationResponses: [ { statusCode: '200', responseParameters: { 'method.response.header.Timestamp': 'integration.response.header.Date', 'method.response.header.Content-Length': 'integration.response.header.Content-Length', 'method.response.header.Content-Type': 'integration.response.header.Content-Type', 'method.response.header.Access-Control-Allow-Headers': "'Content-Type,Authorization'", 'method.response.header.Access-Control-Allow-Methods': "'OPTIONS,POST,PUT,GET,DELETE'", 'method.response.header.Access-Control-Allow-Origin': "'*'", }, }, { statusCode: '400', selectionPattern: '4\\d{2}', responseParameters: { 'method.response.header.Access-Control-Allow-Headers': "'Content-Type,Authorization'", 'method.response.header.Access-Control-Allow-Methods': "'OPTIONS,POST,PUT,GET,DELETE'", 'method.response.header.Access-Control-Allow-Origin': "'*'", }, }, { statusCode: '500', selectionPattern: '5\\d{2}', responseParameters: { 'method.response.header.Access-Control-Allow-Headers': "'Content-Type,Authorization'", 'method.response.header.Access-Control-Allow-Methods': "'OPTIONS,POST,PUT,GET,DELETE'", 'method.response.header.Access-Control-Allow-Origin': "'*'", }, }, ], }, }), { requestParameters: { 'method.request.path.userId': true, 'method.request.path.fileName': true, }, methodResponses: [ { statusCode: '200', responseParameters: { 'method.response.header.Timestamp': true, 'method.response.header.Content-Length': true, 'method.response.header.Content-Type': true, 'method.response.header.Access-Control-Allow-Headers': true, 'method.response.header.Access-Control-Allow-Methods': true, 'method.response.header.Access-Control-Allow-Origin': true, }, }, { statusCode: '400', responseParameters: { 'method.response.header.Access-Control-Allow-Headers': true, 'method.response.header.Access-Control-Allow-Methods': true, 'method.response.header.Access-Control-Allow-Origin': true, }, }, { statusCode: '500', responseParameters: { 'method.response.header.Access-Control-Allow-Headers': true, 'method.response.header.Access-Control-Allow-Methods': true, 'method.response.header.Access-Control-Allow-Origin': true, }, }, ], } ); } }
動作確認(JSON ファイル)
API Gateway で作成した REST API を使って、テキトーな JSON ファイルをアップロードします。その後、アップロードしたファイルを直接 S3 からダウンロードして、中身を確認します。
$ curl -X PUT \ -H "Content-Type: application/json" \ -d '{"hello": "world"}' \ https://<DOMAIN_NAME>/v1/users/sample-user/files/sample.json $ aws s3 cp s3://<BUCKET_NAME>/sample-user/sample.json sample.json download: s3://<BUCKET_NAME>/sample-user/sample.json to ./sample.json $ cat sample.json {"hello": "world"}
上手くアップロードできていることを確認できました。
JSON ファイルをアップロードをしたりするだけであればこれで良いのですが、このままだと画像のアップロードは上手くいきません。少し API の設定を変更する必要があります。
画像のアップロードに対応する
設定変更箇所をハイライト表示しています。
import * as cdk from '@aws-cdk/core'; import * as apigateway from '@aws-cdk/aws-apigateway'; import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; export class ApiGatewayProxyToS3ByCdkStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const projectName: string = this.node.tryGetContext('projectName'); // ★ S3 const bucket = new s3.Bucket(this, 'Bucket', { bucketName: `${projectName}-bucket`, }); // ★ API Gateway const restApiRole = new iam.Role(this, 'Role', { assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), path: '/', }); bucket.grantReadWrite(restApiRole); const restApi = new apigateway.RestApi(this, 'RestApi', { restApiName: `${projectName}-api`, deployOptions: { stageName: 'v1', loggingLevel: apigateway.MethodLoggingLevel.INFO, dataTraceEnabled: true, }, defaultCorsPreflightOptions: { allowOrigins: apigateway.Cors.ALL_ORIGINS, allowMethods: ['POST', 'OPTIONS', 'PUT', 'DELETE'], statusCode: 200, }, // バイナリメディアタイプを設定して、画像を扱えるようする binaryMediaTypes: ['image/*'], }); // リソースを作成する `/users/{userId}/files/{fileName}` const users = restApi.root.addResource('users'); const userId = users.addResource('{userId}'); const files = userId.addResource('files'); const fileName = files.addResource('{fileName}'); // オブジェクトをアップロードするための PUT メソッドを作成する fileName.addMethod( 'PUT', new apigateway.AwsIntegration({ service: 's3', integrationHttpMethod: 'PUT', // アップロード先を指定する path: `${bucket.bucketName}/{folder}/{object}`, options: { credentialsRole: restApiRole, passthroughBehavior: apigateway.PassthroughBehavior.WHEN_NO_MATCH, requestParameters: { // メソッドリクエストのヘッダー Content-Type を 統合リクエストのヘッダーにマッピングする 'integration.request.header.Content-Type': 'method.request.header.Content-Type' // メソッドリクエストのパスパラメータ userId を 統合リクエストのパスパラメータ folder にマッピングする 'integration.request.path.folder': 'method.request.path.userId', // メソッドリクエストのパスパラメータ fileName を 統合リクエストの object にマッピングする 'integration.request.path.object': 'method.request.path.fileName', }, integrationResponses: [ { statusCode: '200', responseParameters: { 'method.response.header.Timestamp': 'integration.response.header.Date', 'method.response.header.Content-Length': 'integration.response.header.Content-Length', 'method.response.header.Content-Type': 'integration.response.header.Content-Type', 'method.response.header.Access-Control-Allow-Headers': "'Content-Type,Authorization'", 'method.response.header.Access-Control-Allow-Methods': "'OPTIONS,POST,PUT,GET,DELETE'", 'method.response.header.Access-Control-Allow-Origin': "'*'", }, }, { statusCode: '400', selectionPattern: '4\\d{2}', responseParameters: { 'method.response.header.Access-Control-Allow-Headers': "'Content-Type,Authorization'", 'method.response.header.Access-Control-Allow-Methods': "'OPTIONS,POST,PUT,GET,DELETE'", 'method.response.header.Access-Control-Allow-Origin': "'*'", }, }, { statusCode: '500', selectionPattern: '5\\d{2}', responseParameters: { 'method.response.header.Access-Control-Allow-Headers': "'Content-Type,Authorization'", 'method.response.header.Access-Control-Allow-Methods': "'OPTIONS,POST,PUT,GET,DELETE'", 'method.response.header.Access-Control-Allow-Origin': "'*'", }, }, ], }, }), { requestParameters: { 'method.request.header.Content-Type': true, 'method.request.path.userId': true, 'method.request.path.fileName': true, }, methodResponses: [ { statusCode: '200', responseParameters: { 'method.response.header.Timestamp': true, 'method.response.header.Content-Length': true, 'method.response.header.Content-Type': true, 'method.response.header.Access-Control-Allow-Headers': true, 'method.response.header.Access-Control-Allow-Methods': true, 'method.response.header.Access-Control-Allow-Origin': true, }, }, { statusCode: '400', responseParameters: { 'method.response.header.Access-Control-Allow-Headers': true, 'method.response.header.Access-Control-Allow-Methods': true, 'method.response.header.Access-Control-Allow-Origin': true, }, }, { statusCode: '500', responseParameters: { 'method.response.header.Access-Control-Allow-Headers': true, 'method.response.header.Access-Control-Allow-Methods': true, 'method.response.header.Access-Control-Allow-Origin': true, }, }, ], } ); } }
動作確認(画像)
動作確認用に、この画像をsample.png
として使用します。
API Gateway で作成した REST API を使って、画像をアップロードします。その後、アップロードしたファイルを直接 S3 からダウンロードして確認します。
$ curl -X PUT \ -H "Content-Type: image/png" \ --data-binary "@./sample.png" \ https://<DOMAIN_NAME>/v1/users/sample-user/files/sample.png $ curl https://<DOMAIN_NAME>/v1/users/sample-user/files/sample.png --output get-sample.png % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 68853 100 68853 0 0 297k 0 --:--:-- --:--:-- --:--:-- 298k
上手くいきました!
おわりに
本記事では、API Gateway で S3 をプロキシして、Bucket にオブジェクトをアップロードしてみました。次回は、オブジェクトの削除にチャレンジします。
今回は以上になります。最後まで読んで頂きありがとうございました!